package com.mapbased.cc;

import java.io.*;
import java.util.*;

import org.dom4j.*;
import com.mapbased.wfw.*;
import com.mapbased.da.FeatureMeta;
import org.springframework.context.ApplicationContext;
import com.mapbased.da.MetaFactory;
import com.mapbased.da.Feature;
import com.mapbased.da.Field;
import com.mapbased.wfw.PageLoader.LoadContext;

/**
 * <p>Title: </p>
 *
 * <p>Description:
 * example:
 *   <table border="10" width="100%" jwcid="com.mapbased.site.user.web.FTable"
 *     datasource="accountLogMetaFactory.getAccountChangeLogByUserId"
 *     pageQueryKeeps="userId,dddg"
 *     >

 *
 *
 *  </p>
 *
 * <p>Copyright: Copyright (c) 2004</p>
 *
 * <p>Company: mapbased.com</p>
 *
 * @author chy_hs(Baily)
 * @version 1.0
 */
public abstract class FeatureTable
    extends Component
{
    int pagelistCount=10;

    /**
     * the featureMeta o get data from
     */
    protected FeatureMeta datasource;
    protected Binding pageCount;
    protected int maxPageCount=1200;

    /**
     * html attributes for table
     */
    private String tableAttributes;

    private String trAttributes;

    private String trAlterAttributes;

    private String theadStr="";

    protected Col[] cols;

    /**
     * Query String Keep at the end of an url when user Click another Page
     */
    private String[] pageQueryKeeps;

    private boolean renderPageList=true;

    protected static class Col
    {
        /**
         * the attributes parse from incoming html
         */

        int fieldIndex= -1;

        Object[] segments;

//        public void writeOut(java.io.Writer w,Feature f)
//            throws IOException
//        {
//            w.write("<td ");
//            if(this.attributes != null)
//            {
//                w.write(this.attributes);
//            }
//            w.write(">");
//
//            if(this.fieldIndex > -1)
//            {
//                w.write(String.valueOf(f.getValue(this.fieldIndex)));
//            }
//            else
//            {
//                FeatureTable.defalutRenderBehavior();
//
//            }
//
//            w.write("</td>");
//
//        }

    }

    /**
     * a component can choose to parse an element or not.
     * =================Attributes======================<br>

     * featureMeta:metaFactory.featureMeta<br>

     *
     *
     *======================================================
     * @param ele Element
     * @param loader PageLoader
     * @return Element or null:if wanna page loader parse the
     *   component,return the ele, else return null
     * @todo Implement this com.mapbased.wfw.Component method
     */
    public Element parseXml(Element ele,
                            com.mapbased.wfw.PageLoader.LoadContext lc)

    {

        this.fetchDataSource(ele,lc);
        String ts=this.fetchAttribute(ele,"nopagelist");
        if(ts != null && !ts.equalsIgnoreCase("false"))
        {
            this.renderPageList=false;

        }
        String maxpc=this.fetchAttribute(ele,"maxPageCount");
        if(maxpc != null && maxpc.length() > 0)
        {
            this.maxPageCount=Integer.parseInt(maxpc);
        }
        final String cs=this.fetchAttribute(ele,"pageCount");
        if(cs != null && !cs.trim().equals(""))
        {
            if(cs.indexOf('@') >= 0)
            {
                LoadContext nlc=new LoadContext(lc);

                lc.loader.parseBinding(cs,nlc);
                pageCount=(Binding)nlc.segments.get(0);
            }
            else
            {

                this.pageCount=new Binding()
                {
                    public Object getValue(RenderContext context)
                    {
                        return Integer.valueOf(cs.trim());
                    }
                };
            }
        }

        /////////////////////////////////////////////////////////////////
        String qs=this.fetchAttribute(ele,"pageQueryKeeps");

        if(qs == null)
        {
            this.pageQueryKeeps=new String[0];
        }
        else
        {
            this.pageQueryKeeps=qs.split(",");
        }
        for(int i=0;i < this.pageQueryKeeps.length;i++)
        {
            this.pageQueryKeeps[i]=this.pageQueryKeeps[i].trim();
        }
        /////////////////////////////////////////////////////////////////

        this.tableAttributes=this.getElementAttributes(ele);
        this.handleTR(ele,lc);

        return null;
    }

    protected void fillCol()
    {
        Field[] fls=this.datasource.getFields();
        if(fls != null)
        {
            this.fillCol(fls);
        }
    }

    synchronized private void fillCol(Field[] fls)
    {
        this.cols=new Col[fls.length];
        for(int i=0;i < fls.length;i++)
        {
            Col col=new Col();
            col.fieldIndex=i;
            this.cols[i]=col;
        }
        if(this.theadStr == null || this.theadStr.equals(""))
        {
            StringBuffer sb=new StringBuffer();
            sb.append("<thead><tr>");
            for(int i=0;i < fls.length;i++)
            {
                sb.append("<th>");
                sb.append(
                    com.mapbased.wfw.Util.replaceToXml(fls[i].getName()));
                sb.append("</th>");
            }

            sb.append("</tr></thead>");
            this.theadStr=sb.toString();

        }

    }

    /**
     * Parse td in the first TR of tbody
     * the content  in second td is discard
     *
     * @param ele Element
     */
    private void handlerTD(Element ele,
                           com.mapbased.wfw.PageLoader.LoadContext lc)
    {

        if(ele == null || ele.elements().size() == 0)
        {
            fillCol();

        }
        else
        {
            List l=ele.elements();
            this.cols=new Col[l.size()];
            for(int i=0;i < l.size();i++)
            {
                com.mapbased.wfw.PageLoader.LoadContext nlc=new
                    com.mapbased.wfw.PageLoader.LoadContext(lc);

                List segs=nlc.segments;
                PageLoader.StringBufferBag last
                    =nlc.last;

                lc.loader.parseElement((Element)l.get(i),
                                       nlc);
                segs.add(last.getString());
                this.cols[i]=new Col();
                this.cols[i].segments=segs.toArray();
            }

        }

    }

    /**
     * parse TR in table component
     * @param ele Element
     */
    private void handleTR(Element element,
                          com.mapbased.wfw.PageLoader.LoadContext lc)
    {
        Element thead=element.element("thead");
        if(thead != null)
        {
            this.theadStr=thead.asXML();
        }
        Element tbody=element.element("tbody");
        Element ele=element;
        if(tbody != null)
        {
            ele=tbody;

        }

        List l=ele.elements("tr");
        if(l.size() == 0)
        {
            l=ele.elements("TR");
        }

        if(l.size() > 0)
        {
            Element e=(Element)l.get(0);
            this.trAttributes=this.getElementAttributes(e);
            this.handlerTD(e,lc);
            if(l.size() > 1)
            {
                this.trAlterAttributes
                    =this.getElementAttributes((Element)l.get(1));
            }

        }
        else
        {
            this.handlerTD(null,lc);
        }

    }

    /**
     *
     * @param ele Element
     * @param loader PageLoader
     * @return:String value ,won't be null
     */
    private String getElementAttributes(Element ele)
    {
        List l=ele.attributes();
        StringBuffer sb=new StringBuffer(l.size() * 10 + 1);
        for(int i=0;i < l.size();i++)
        {
            Attribute a=(Attribute)l.get(i);
            sb.append(" ");
            sb.append(a.asXML());
        }
        return sb.toString();

    }

    /**
     * Fetch a attribute from given element and delete it
     * @param ele Element
     * @return String:null if cannot find the attribute
     */
    private String fetchAttribute(Element ele,String attributeName)
    {
        Attribute a=ele.attribute(attributeName);
        if(a == null)
        {
            return null;
        }
        ele.remove(a);
        return a.getValue();

    }

    /**
     * fetch datasouce from element
     * @param ele Element
     * @param loader PageLoader
     */

    protected void fetchDataSource(Element ele,LoadContext loader)
    {
        Attribute a=ele.attribute("featureMeta");

        // String fm=ele.attributeValue("featureMeta");
        if(a == null)
        {
            a=ele.attribute("datasource");
        }
        if(a == null)
        {
            throw new java.lang.RuntimeException(
                "Cannot find featureMeta for FeatureTable component");
        }
        ele.remove(a);
        String fm=a.getValue();

        int dindex=fm.indexOf('.');
        String metafactory=fm.substring(0,dindex).trim();
        String fmeta=fm.substring(dindex + 1).trim();
        this.fetchDataSource(metafactory,fmeta);
//        MetaFactory metaFactory=
//            (MetaFactory)this.getApplicationContext().getBean(metafactory);
//        if(metaFactory == null)
//        {
//            throw new java.lang.NullPointerException(
//                "Cannot find MetaFactory:" + metafactory);
//        }
//        this.datasource=metaFactory.get(fmeta);
//        if(this.datasource == null)
//        {
//            throw new java.lang.NullPointerException(
//                "Cannot find FeatureMeta:" + fmeta);
//        }
    }

    protected void fetchDataSource(String metafactory,String fmeta)
    {
        MetaFactory metaFactory=
            (MetaFactory)this.getApplicationContext().getBean(metafactory);
        if(metaFactory == null)
        {
            throw new java.lang.NullPointerException(
                "Cannot find MetaFactory:" + metafactory);
        }
        this.datasource=metaFactory.get(fmeta);
        if(this.datasource == null)
        {
            throw new java.lang.NullPointerException(
                "Cannot find FeatureMeta:" + fmeta);
        }

    }

    /**
     * give child class a change to get featureMeta in there own way
     * @param ele Element
     * @return FeatureMeta
     */
    protected abstract ApplicationContext getApplicationContext();

    protected String getNextPageCaption()
    {
        return "下一页";
    }

    protected String getPriorPageCaption()
    {
        return "上一页";
    }

    protected String getGoCaption()
    {

        return "第N页";
    }

    protected boolean isRenderPageList()
    {
        return this.renderPageList;
    }

    /**
     * default using context's request map for a DA query
     * sometimes may be need add some additional request param
     * see get userId from session
     */
    abstract protected void prepareQuery(RenderContext context);

    protected List getFeatures(RenderContext context)
    {
        this.prepareQuery(context);
        return this.datasource.query(context.getRequestMap());
    }

    /**
     * render
     *
     * @param context RenderContext
     * @throws IOException
     * @todo Implement this com.mapbased.wfw.Component method
     */
    public void render(RenderContext context)
        throws IOException
    {

        java.io.Writer w=
            context.getOutputWriter();
        int currentPage=context.getSureSingleInt("pageIndex");
        if(currentPage <= 0)
        {

            context.putParam("pageIndex",new Integer(0));
        }
        else if(currentPage > this.maxPageCount)
        {

            context.putParam("pageIndex",new Integer(this.maxPageCount));
        }

        List features=this.getFeatures(context);
        if(features == null)
        {
            return;
        }
        w.write("<table");
        w.write(this.tableAttributes);
        w.write(" >");

        this.renderTHead(w);

        if(this.cols == null)
        {
            this.fillCol();
        }

        if(features.size() == 0)
        {
            //todo:write no list
        }
        else
        {
            for(int i=0;i < features.size();i++)
            {
                Object f=features.get(i);
                context.getRequestMap().put("feature",f);
                w.write("<tr ");

                String s=i % 2 == 0 ? this.trAttributes :
                    this.trAlterAttributes;
                if(s == null)
                {
                    s=this.trAttributes;

                }
                if(s != null)
                {
                    w.write(s);
                }
                w.write(" >");

                for(int j=0;j < this.cols.length;j++)
                {
                    Col c=cols[j];
                    if(c.fieldIndex >= 0)
                    {
                        w.write("<td>");
                        w.write(com.mapbased.wfw.Util.replaceToXml(
                            String.valueOf(((Feature)f).getValue(c.fieldIndex))));
                        w.write("</td>");
                    }
                    else
                    {
                        defalutRenderBehavior(context,c.segments);
                    }
                }

                w.write("</tr>");

            }
        }

        if(this.isRenderPageList())
        {
            this.renderPageList(context,features.size() == 0);
        }

        w.write("</table>");

    }

    protected void renderPageList(RenderContext rc,boolean isend)
        throws java.io.IOException

    {
        String pp=rc.getRequest().getServletPath();
        int last=pp.lastIndexOf('/');
        int currentPage=rc.getSureSingleInt("pageIndex");

        StringBuffer sb=new StringBuffer(this.pageQueryKeeps.length * 10);
        for(int i=0;i < this.pageQueryKeeps.length;i++)
        {
            String s=this.pageQueryKeeps[i];
            sb.append("&");
            sb.append(s);
            sb.append("=");
            sb.append(java.net.URLEncoder.encode(rc.getSureSingleParam(s),
                                                 "utf-8"));
        }
        String urlend=sb.toString();

        String pn=pp.substring(last + 1);
        java.io.Writer w=rc.getOutputWriter();
        w.write("<tfoot>");
        w.write("<tr> <th align=\"center\" colspan=\"");
        w.write(String.valueOf(this.cols.length));
        w.write("\" >");

        w.write("<form action=\"");
        w.write(pn);

        w.write("\" method=\"GET\">");

/////////////////////////////////////////////////////

        if(currentPage > 0)
        {
            w.write("[<a href=\"");
            w.write(pn);
            w.write("?pageIndex=");
            w.write(String.valueOf(currentPage - 1));
            w.write(urlend);

            w.write("\" >");
            w.write(this.getPriorPageCaption());
            w.write("</a>]");
        }

//////////////////////////////////////////////////
        int t=currentPage - this.pagelistCount / 2;
        int low=0;

        if(t > 0)
        {
            low=t;
        }
        int up=low + pagelistCount;
        if(this.pageCount != null)
        {
            Number o=(Number)this.pageCount.getValue(rc);
            if(o != null)
            {
                int pc=o.intValue();
                // low=0;
                up=Math.min(pc,up);
                isend=isend || (pc) <= currentPage;
                if(currentPage > up)
                {
                    currentPage=up;
                }

            }

        }
        else if(isend)
        {
            up=currentPage;
        }
        int l=up - this.pagelistCount;
        low=l >= 0 ? l : 0;

        for(int i=low;i <= up;i++)
        {
            if(i == currentPage)
            {
                w.write("[<b>");

                w.write(String.valueOf(i + 1));

                w.write("</b>]");
                continue;

            }
            w.write("[<a href=\"");
            w.write(pn);
            w.write("?pageIndex=");
            w.write(String.valueOf(i));
            w.write(urlend);

            w.write("\" >");

            w.write(String.valueOf(i + 1));

            w.write("</a>]");
////////////////////////////////////////////////////////

        }

        /////////////next///////////////////////////

        if(!isend)
        {
            w.write("[<a href=\"");
            w.write(pn);
            w.write("?pageIndex=");
            w.write(String.valueOf(currentPage + 1));
            w.write(urlend);

            w.write("\" >");
            w.write(this.getNextPageCaption());
            w.write("</a>]");
        }

//////////////////////////////////////////////////
        /**
         * <form action="" method="GET">
             <input type="text" name="" size="8" value="" />
             <input type="submit" value="第N页" />
         </form>

         */

//        if(!(isend && currentPage < 9))
//
//        {
//            for(int i=0;i < this.pageQueryKeeps.length;i++)
//            {
//                String s=this.pageQueryKeeps[i];
//                w.write("<input type=\"hidden\" name=\"");
//                w.write(s);
//                w.write("\" value=\"");
//                w.write(rc.getSureSingleParam(s));
//                w.write("\" />");
//            }
//            w.write("<input type=\"text\" name=\"pageIndex\" size=\"8\" value=\"");
//            w.write(String.valueOf(currentPage + 1));
//            w.write("\" /><input type=\"submit\" value=\"");
//            w.write(this.getGoCaption());
//            w.write("\" /> </form>");
//        }
        //////////////////////////////////////////////////////////



        w.write("</th></tr>");
        w.write("</tfoot>");

    }

    protected void renderTHead(java.io.Writer w)
        throws IOException
    {
        if(this.theadStr != null)
        {
            w.write(this.theadStr);
        }
    }

    /**
     * renderHeader
     *
     * @param context RenderContext
     * @param sb StringBuffer
     * @param m Map
     * @todo Implement this com.mapbased.wfw.Component method
     */
    public void renderHeader(RenderContext context,StringBuffer sb,Map m)
    {
    }

}
